/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/	
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Author:
 * Winston Huang
 * Mike Schmitt
 *
 * All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// TODO:
// add option to choose where to save password
// logout after each minute usage retrieval


//window.addEventListener('load', initChecker, false);
//window.addEventListener('unload', delegateNewMaster, false);

var gVerizonMinutesUsed = null;

function verizonLoad()
{
  gVerizonMinutesUsed = new VerizonMinutesUsed();
  gVerizonMinutesUsed.initChecker();
}

function verizonUnload()
{
  gVerizonMinutesUsed.delegateNewMaster();
  gVerizonMinutesUsed = null;
}

function verizonOptionEditLoad()
{
  gVerizonMinutesUsed = new VerizonMinutesUsed();
  gVerizonMinutesUsed.optionEditLoad();
}

function VerizonMinutesUsed()
{
  this.wrappedJSObject = this;
  return this;
}

VerizonMinutesUsed.prototype = {

loggedInByMe : false,
loggedInFailed : false,
async : true,
httpReq:null,
intervalId : null,
// the current design sequence all the http requests. therefore we can use one timeout id. 
timeoutId : null,
minutesStatusBarId : "verizon-minutes-statusbar",
minutesStatusBar : null,
tooltipLabels : null,
tooltipLabelCount : 0,

prefBranch: null,
// phoneSite : "https://www.verizonwireless.com/b2c/index.jsp",
// list of sites to which firefox associate the verizon user/password. when reading user/password, the first site on the list with a match is used. when setting password, all sites are set with the same user/password
passwordSites : new Array ("http://www.verizonwireless.com", "https://www.verizonwireless.com"),
phoneLogoBig : "chrome://verizon-minutes/content/verizon-logo.gif",
phoneLogoSmall : "chrome://verizon-minutes/content/vzwlogo.gif",
billingURLBase : "billpay.verizonwireless.com/vzw/overview/submitOverviewAccountEquipment.do?displayFamilyShareMinutes=N",
familyBillingURLBase: "billpay.verizonwireless.com/vzw/overview/submitOverviewAccountEquipment.do?displayFamilyShareMinutes=Y",
loginURL : "https://login.verizonwireless.com/amserver/UI/Login",
logoutURLBase : "billpay.verizonwireless.com/vzw/logout.do",
aboutURL : "http://verizon-minutes-used.blogspot.com",
usernameFormField : "IDToken1",
passwordFormField : "IDToken2",

pwdmgr: null,
pwdmgrInternal: null,
usePwdMgr:true,
oldUsername: null,
defaultFrequency : 1000*60*30,
defaultHttpTimeout : 30000,
defaultThreshold : -1,
defaultLogo : 0,

checkingInProgress : false,

lastResponseFile: null,

observerService: null,

finishedChecking: function () {
    this.debug("finished checking");
    this.checkingInProgress = false;
},

initChecker: function () 
{
    this.debug("in initChecker");
    try
    {
        this.minutesStatusBar = document.getElementById(this.minutesStatusBarId);
        
        this.initOption();

        this.observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
        
        this.lastResponseFile = Components.classes["@mozilla.org/file/directory_service;1"]
            .getService(Components.interfaces.nsIProperties)
            .get("ProfD", Components.interfaces.nsIFile);

        this.lastResponseFile.append("verizon_last_response.html");
        if (!this.lastResponseFile.exists()) {
            this.lastResponseFile.create(this.lastResponseFile.FILE_TYPE, 0700);
        }
        
        var loggedIn = false;
        
        // check if a checker has been started
        var windowManager= this.getWindowManager();
        var enumerator = windowManager.getXULWindowEnumerator( null );
        while ( enumerator.hasMoreElements()  )
        {
            var xulWindow = enumerator.getNext();
            var win = this.getDOMWindow(xulWindow);
            
            if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId != null)
            {
                this.debug(win + " intervalId is not null");
                loggedIn = true;
                
                // set the icon to the same as the master window
                if (this.minutesStatusBar != null) {
                    var myBar = this.minutesStatusBar;
                    var oldBar = xulWindow.docShell.contentViewer.DOMDocument.getElementById(this.minutesStatusBarId);
                    this.debug("copying status bar");
                    myBar.src = oldBar.src;
                    myBar.setAttribute("label", oldBar.getAttribute("label"));
                    myBar.setAttribute("height", oldBar.getAttribute("height"));
                    myBar.setAttribute("style", oldBar.getAttribute("style"));
                    if (oldBar.getAttribute("tooltiptext") != "") {
                        this.debug("copying tooltiptext");
                        myBar.setAttribute("tooltiptext", oldBar.getAttribute("tooltiptext"));
                    } else {
                        this.debug("copying tooltip");

                        if (myBar.hasAttribute("tooltiptext")) {
                            myBar.removeAttribute("tooltiptext");
                        }
                        
                        var tooltipId = oldBar.getAttribute("tooltip");
                        var oldTooltip = xulWindow.docShell.contentViewer.DOMDocument.getElementById(tooltipId);

                        // if using clone, firefox would crash when closing the 2nd window. therefore we are manually copying the existing tooltip
                        // var tooltip = oldTooltip.cloneNode(true);

                        tooltip = this.newTooltip(document, myBar);
                        for (var i = 0; i < oldTooltip.childNodes.length; i++) {
                            var lbl = this.cloneTooltipLabel(document, oldTooltip.childNodes.item(i));
                            tooltip.appendChild(lbl);
                        }
                        
                        document.getElementById("mainPopupSet").appendChild(tooltip);
                        myBar.setAttribute("tooltip", tooltip.getAttribute("id"));                        
                    }
                }
            }
        }

        if (document.getElementById(this.minutesStatusBarId)) {
            if (!loggedIn) {
                this.start();
            }
        }
    }
    catch (ex)
    {
        this.warn(ex);
    }
    
},

// if this window is the master and it's closed, we need to make another window the master
delegateNewMaster: function()
{
    try
    {
        this.debug("trying to delegate new master");
        // check if this is the master
        if (this.intervalId != null)
        {
            var newMasterFound = false;
            var windowManager= this.getWindowManager();
            var enumerator = windowManager.getXULWindowEnumerator( null );
            while ( enumerator.hasMoreElements() && !newMasterFound )
            {
                var xulWindow = enumerator.getNext();
                var win = this.getDOMWindow(xulWindow);
                    
                // make sure we're not dealing with the current window
                if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId == null)
                {
                    this.debug("found new master " + win);
                    win.gVerizonMinutesUsed.scheduleMinutesChecking(win);
                    newMasterFound = true;
                }
            }
        }
    }
    catch (ex)
    {
        this.warn(ex);
    }
},

// This is the observerService's observe listener.
observe: function(subject, topic, data) {
    if (topic == 'http-on-modify-request') {
        var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
        var url = "https://" + httpChannel.originalURI.host + httpChannel.originalURI.path;

        // extra check to make sure we only se the referer value for login request
        if (this.loginURL == url) {
            httpChannel.setRequestHeader('Referer', 'http://www.verizonwireless.com/b2c/index.html', false);
        }
    }
},

login: function()
{
    try {
        this.debug('trying to login');
        var loginData = this.getLoginData();
        var username = loginData.username;
        var pass = loginData.password;
        var number = loginData.number;  //NUMBER IS NOT MANDATORY
        if (username != null && pass != null && username.length > 0 && pass.length > 0 ) {    
            this.observerService.addObserver(this, "http-on-modify-request",   false);

            this.httpReq = new XMLHttpRequest();
            this.httpReq.open("POST", this.loginURL, this.async);
            this.httpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            // this doesn't work anymore in firefox 2.0. therefore using observer to add the value for outbound http request instead
            //            this.httpReq.setRequestHeader('Referer', 'http://www.verizonwireless.com/b2c/index.html');
            this.setReqTimeout(this.loginURL);

            var module = this;
            if (this.async) {
                this.httpReq.onload = function() {module.parseLogin()};
            }

            this.httpReq.send(this.usernameFormField + "=" + username + '&' + this.passwordFormField + '=' + pass);
            if (!this.async) {
                this.parseLogin();
            }
            return true;
        }
        else {
            this.finishedChecking();
            this.setStatusBarAttribute("label", "invalid user name or password");
            this.setStatusBarAttribute("height", 1);
            this.setStatusBarAttribute("src", this.getPhoneLogo());
            this.error("Verizon Web site username and/or password are missing from the option menu. Please register an account at http://www.verizonwireless.com and go to this extension's option menu to set the username and password. They will be securly saved in the Firefox's password manager.");
            return false;
        }
    }
    catch (ex)
    {
        this.finishedChecking();
        this.warn(ex);
        return false;
    }
},

checkMinutesWithLogout: function() {
    if (this.checkingInProgress) {
        this.debug("another checking is still in progress. exiting this one.");
        return;
    }
    this.checkingInProgress = true;
    this.loggedInByMe = false;
    this.loggedInFailed = false;
    this.checkMinutes();
},
        
afterLogout: function() {
    this.debug("logging out finished");
    this.clearReqTimeout();
    this.checkMinutes();
},

prependRegionToURL : function(url) {
    if (this.getRegion() == 0) {
        return "https://e" + url;
    } else {
        return "https://w" + url;
    }
},
    
getLogoutURL : function() {
    return this.prependRegionToURL(this.logoutURLBase);
},

getPhoneLogo : function() {
    if (this.getLogoWhich() == 0) {
        return this.phoneLogoBig;
    } else if (this.getLogoWhich() == 1) {
	return this.phoneLogoSmall;
    } else {
        return null;
    }
},
    
getBillingURL : function() {
    var url = null;
    if (this.getShowFamilyTotal() != 0) {
        url = this.familyBillingURLBase;
    } else {
        url = this.billingURLBase;
    }

    // append number even for family plan url
    var number = null;
    if (this.prefBranch.prefHasUserValue("number")) {
        number = this.prefBranch.getCharPref("number");
    }
    if (number != null && number != "") {
        url += "&mtn=" + number;        
    }
    return this.prependRegionToURL(url);
},

checkMinutes: function ()
{
    this.checkMinutesFromBillingURL(this.getBillingURL());
},

checkMinutesFromBillingURL: function (billingURL)
{
    try {
        this.debug("checking minutes async = " + this.async );
        
        this.httpReq = new XMLHttpRequest();
        this.httpReq.open("GET", billingURL, this.async);
        // this.httpReq.setRequestHeader("Pragma", "no-cache");
        this.setReqTimeout(billingURL);
        var module=this;
        if (this.async) {
            this.httpReq.onload=function() {module.parseMinutes()};
        }
        this.httpReq.send(null);
        if (!this.async) {
            this.parseMinutes();
        }
    } catch (ex) {
        this.finishedChecking();
        this.warn(ex);
    }
},

parseMinutes: function(evt) {
    try {
        this.debug("parsing minutes");
        this.clearReqTimeout();
        
        var responseText = this.httpReq.responseText;

        // truncate the response cache file first
        var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
            .createInstance(Components.interfaces.nsIFileOutputStream);
        foStream.init(this.lastResponseFile, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
        foStream.close();

        // request is for retrieving the billing site

        // if the page is a valid xml
        // var response = this.httpReq.responseXML.documentElement;

        // TODO should try dom, it's better than regular expression
        //          var index = responseText.indexOf("<!DOCTYPE");
        //          responseText = responseText.substring(index);
        //          var response = (new DOMParser()).parseFromString(responseText, "text/xml");
        //          var form = response.getElementById("InvoiceDate");

        // TODO better way to check the need for a login
        // the first condition is true if you haven't logged in yet
        // the second condition is true if you have logged out or provided a wrong password before
        if (responseText.indexOf("System Failure") >= 0 ||
            (responseText.indexOf("IDToken1") >= 0 && responseText.indexOf("IDToken2") >= 0) ||
            responseText.indexOf("My Account Online Login") >= 0 ||
            responseText.indexOf("Forgot User Name or Password") >= 0) {

            // if the phone number is not correct, it will default to the main one
            if (this.loggedInByMe) {
                var number = this.getLoginData().number;
                if (number != null && number != "") {
                    this.finishedChecking();                    
                    // it also happens after a long time when the session expires
                    this.warn("Please check if the phone number " + number + " is valid in the option menu");
                    return;
                }
            } 

            if (this.loggedInFailed) {
                this.finishedChecking();
                this.setStatusBarAttribute("label", "failed to login");
                this.setStatusBarAttribute("height", 1);
                this.setStatusBarAttribute("src", this.getPhoneLogo());

                this.warn("Failed to login. Make sure your region setting is correct in the option menu.");
                return;
            }

            this.loggedInFailed = true;
            // login will asynchronously login and if login is successful, call checkMinutes again
            this.login();
            return;
        } 

        if (responseText.indexOf("The System is currently unable to display your current balance") >= 0 ||
            responseText.indexOf("My Account is temporarily unavailable") >= 0) {
            this.finishedChecking();
            this.warn("Verizon site is currently unable to display your current balance");
            return;
        }

        this.setLastUpdatedToNow();
        this.parseResponseText(responseText);
        foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
            .createInstance(Components.interfaces.nsIFileOutputStream);
        foStream.init(this.lastResponseFile, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
        foStream.write(responseText, responseText.length);
        foStream.close();

        this.finishedChecking();
        
    } catch (ex) {
        this.finishedChecking();
        this.warn(ex);
    }
},

parseResponseText: function(responseText) {
    // cannot get the "s" modifier in javascript to have . match new line
    var result = this.parse(responseText, ">Peak<(.*\\r\\n)+\\t([\\d,]+)\\r\\n<\\/td>(\\r\\n)+<\\/tr>(.*\\r\\n)*.*>Off\\ Peak<(.*\\r\\n)+\\t([\\d,]+)\\r\\n<\\/td>(\\r\\n)+<\\/tr>(.*\\r\\n)*.*>Weekend<(.*\\r\\n)+?\\t([\\d,]+)\\r\\n<\\/td>(\\r\\n)+<\\/tr>");
    if (result && result.length > 0) {
        var peak = this.removeCommas(result[2]);
        var offPeak = this.removeCommas(result[6]);
        var weekend = this.removeCommas(result[10]);
        
        this.debug("peak = " + peak);
        this.debug("offPeak = " + offPeak);
        this.debug("weekend = " + weekend);

        var peakNum = 0;
        try {
            peakNum = parseInt(peak,10);
        } catch (err) {
            this.debug("exception parsing number " + peak + ": " + err);
        }

        var offPeakNum = 0;
        try {
            offPeakNum = parseInt(offPeak,10);
        } catch (err) {
            this.debug("exception parsing number " + offPeak + ": " + err);
        }

        var summary;
        if (this.getSummaryWhat() == 0) {
            summary = peakNum;
        } else {
            summary = peakNum + offPeakNum;
        }

        this.setStatusBarAttribute("label", summary);
        if (this.getThreshold() > 0 && summary >= this.getThreshold()) {
            this.setStatusBarAttribute("style", "color:rgb(255,0,0)");
        } else {
            this.setStatusBarAttribute("style", "");
        }
        
        this.setStatusBarAttribute("height", 1);
        this.setStatusBarAttribute("src", this.getPhoneLogo());

        var inCalling = null;
        var inCallingOffPeak = null;
        var inTxtSent = null;
        var inTxtReceived = null;
        var otherDomesticTxtSent = null;
        var otherDomesticTxtReceived = null;
        var inPixSent = null;
        var inPixReceived = null;
        var otherPixSent = null;
        var otherPixReceived = null;
        var lastCall = "N/A";
        var lastTxtMessage = "N/A";
        var lastPixMessage = "N/A";
        var phoneNumber = null;
        var projectedTotal = null;
        var lastStatementDate = null;
        var dayToNextCycle = null;
        
        result = this.parse(responseText, ">IN\\ Calling(.*\\r\\n)+?\\t([\\d,]+)\\r\\n<\\/td>(\\r\\n)+<\\/tr>(.*\\r\\n)*.*>IN\\ Calling\\ Off\\ Peak<(.*\\r\\n)+?\\t([\\d,]+)\\r\\n<\\/td>(\\r\\n)+<\\/tr>");
        if (result != null) {
            inCalling = this.removeCommas(result[2]);
            inCallingOffPeak = this.removeCommas(result[6]);
            this.debug("in calling = " + inCalling);
            this.debug("in calling off peak= " + inCallingOffPeak);            
        } else {
            this.debug("failed to parse in calling");
        }

        result = this.parse(responseText, ">IN\\ Text<.*\\r\\n.*\\r\\n\\t([\\d,]+)(.*\\r\\n)+?\\t([\\d,]+)");
        if (result != null) {
            inTxtSent = this.removeCommas(result[1]);
            inTxtReceived = this.removeCommas(result[3]);
            this.debug("in txt sent = " + inTxtSent);
            this.debug("in txt received = " + inTxtReceived);
        } else {
            this.debug("failed to parse in txt");
        }

        result = this.parse(responseText, ">Other\\ Domestic\\ Text<.*\\r\\n.*\\r\\n.*\\t([\\d,]+)(.*\\r\\n)+?\\t([\\d,]+)");
        if (result != null) {
            otherDomesticTxtSent = this.removeCommas(result[1]);
            otherDomesticTxtReceived = this.removeCommas(result[3]);
            this.debug("other domestic txt sent = " + otherDomesticTxtSent);
            this.debug("other domestic txt received = " + otherDomesticTxtReceived);
        } else {
            this.debug("failed to parse domestic txt");
        }
        
        result = this.parse(responseText, ">IN\\ PIX/FLIX<.*\\r\\n.*\\r\\n.*\\t([\\d,]+)(.*\\r\\n)+?\\t([\\d,]+)");
        if (result != null) {
        	inPixSent = this.removeCommas(result[1]);
        	inPixReceived = this.removeCommas(result[3]);
        	this.debug("IN pix sent = " + inPixSent);
        	this.debug("IN pix received = " + inPixReceived);
        } else {
        	this.debug("failed to parse IN pix");
        }

        result = this.parse(responseText, ">Other\\ PIX/FLIX<.*\\r\\n.*\\r\\n.*\\t([\\d,]+)(.*\\r\\n)+?\\t([\\d,]+)");
        if (result != null) {
            otherPixSent = this.removeCommas(result[1]);
            otherPixReceived = this.removeCommas(result[3]);
            this.debug("other pix sent = " + otherDomesticTxtSent);
            this.debug("other pix received = " + otherDomesticTxtReceived);
        } else {
            this.debug("failed to parse other pix");
        }

        result = this.parse(responseText, "Last\\ Call&nbsp;(\\d+/\\d+/\\d+),&nbsp;(\\d+:\\d+\ [AP]M)");
        if (result != null) {
            lastCall = result[1] + " " + result[2];
            this.debug("last call = " + lastCall);
        } else {
            this.debug("failed to parse last call");
        }

        result = this.parse(responseText, "Last\\ Text\\ Message&nbsp;(\\d+/\\d+/\\d+),&nbsp;(\\d+:\\d+\ [AP]M)");
        if (result != null) {
            lastTxtMessage = result[1] + " " + result[2];
            this.debug("last txt message = " + lastTxtMessage);
        } else {
            this.debug("failed to parse last txt");
        }
        
        result = this.parse(responseText, "Last\\ PIX/FLIX Message&nbsp;(\\d+/\\d+/\\d+),&nbsp;(\\d+:\\d+\ [AP]M)");
        if (result != null) {
        	lastPixMessage = result[1] + " " + result[2];
        	this.debug("last pix message = " + lastPixMessage);
        } else {
        	this.debug("failed to parse last pix");
        }

        result = this.parse(responseText, ">Wireless\\ No.*\\r\\n.*\\r\\n.*>(\\d+\\-\\d+\\-\\d+)<");
        if (result != null) {
            phoneNumber = result[1];
            this.debug("phone number = " + result);
        } else {
            this.debug("failed to parse Wireless No.");
        }

        result = this.parse(responseText, "last\\ statement\\ date,\\s*(\\d+)/(\\d+)/(\\d+)");
        if (result != null) {
            var now = new Date();
            var year = parseInt(result[3],10)+2000;
            var month = parseInt(result[1],10);
            var day = parseInt(result[2],10);
            var last = new Date(year, month-1, day);
            var diff = now - last;
            diff=Math.round(diff/1000/60/60/24);
            projectedTotal = Math.round(summary*30/diff);
            lastStatementDate = result[1] + "/" + result[2] + "/" + result[3];
            dayToNextCycle = 31-diff;
        } else {
            this.debug("failed to parse last statement date");
        }
                            
        this.tooltipLabels = new Array();
        this.tooltipLabelCount = 0;
        this.addTooltipLabel("Wireless Number: " + phoneNumber, "header");
        if (lastStatementDate != null) {
            this.addTooltipLabel("Last Statement Date: " + lastStatementDate);
        }
        if (dayToNextCycle != null) {
            this.addTooltipLabel("Appr. " + dayToNextCycle + " Days to Next Cycle");
        }

        this.addTooltipLabel("------CALLs------", "header");
        this.addTooltipLabel("Normal Calls:");
        this.addTooltipLabel("Peak: " + peak + "   Off-Peak: " + offPeak, "indent");
        if (projectedTotal != null) {
            this.addTooltipLabel("Projection for this month: " + projectedTotal, "indent");
        }
        if (weekend != "0") {
        	this.addTooltipLabel("Weekend: " + weekend, "indent");
        } else {
        	//this.addTooltipLabel("Weekend: N/A (included in off-peak)", "indent");  // mike says: this works, but i think it's just too cluttery; i think 'weekend' should be left off until the value is greater than 0.
        	this.debug("weekend minutes not included in tooltip");
        }
        	
        if (inCalling != null) {
            this.addTooltipLabel("'IN' Calls:", null),
            this.addTooltipLabel("'IN' Peak: " + inCalling + "   'IN' Off-Peak: " + inCallingOffPeak, "indent");
        }
        this.addTooltipLabel("Most Recent Call: " + lastCall);
        if (inTxtSent != null || otherDomesticTxtSent != null || inPixSent != null || otherPixSent != null) {
            this.addTooltipLabel("------TXTs------", "header");
            if (inTxtSent != null) {
                this.addTooltipLabel("'IN' TXTs: " + inTxtSent + " Sent,   " + inTxtReceived + " Received.");
            }
            if (otherDomesticTxtSent != null) {
                this.addTooltipLabel("Other Domestic TXTs: " + otherDomesticTxtSent + " Sent,   " + otherDomesticTxtReceived + " Received.");
            }
            if (inPixSent != null) {
            	this.addTooltipLabel("'IN' PIX/FLIX: " + inPixSent + " Sent,   " + inPixReceived + " Received.");
            }
            if (otherPixSent != null) {
                this.addTooltipLabel("Other PIX/FLIX: " + otherPixSent + " Sent,   " + otherPixReceived + " Received.");
            }
            if (lastTxtMessage != null) {
            	this.addTooltipLabel("Most Recent TXT: " + lastTxtMessage);
            }
            if (lastPixMessage != null) {
            	this.addTooltipLabel("Most Recent PIX/FLIX: " + lastPixMessage);
            }
        }
        
        this.setStatusBarTooltip(this.tooltipLabels);

    } else {
        this.warn("failed to parse the minutes used");
    }

},

removeCommas: function(myString ) {
    myString=myString.replace(/,/g,"");
    return myString;
},

// parse the responseText of the default login and return a list of all phone numbers. if there is only one phone number, return null    
getAllPhoneNumbers: function(responseText) {
    var start = responseText.indexOf("<div class=\"myMtnList\">");
    var allPhoneNumbers;
    if (start >= 0) {
        var end = responseText.substring(start).indexOf("</div>");
        if (end > 0) {
            var sub = responseText.substring(start, end+start);
            allPhoneNumbers = new Array();
            var position = 0;
            for (;;) {
                var valueIndex1 = sub.indexOf("value=\"");
                if (valueIndex1 < 0) {
                    break;
                }
                valueIndex1 += 7;
                var valueIndex2 = sub.substring(valueIndex1).indexOf("\"");
                valueIndex2 += valueIndex1;
                allPhoneNumbers[position++] = sub.substring(valueIndex1, valueIndex2);
                this.debug("found phone number " + allPhoneNumbers[position-1]);
                sub = sub.substring(valueIndex2);
            }
        }
    }
    return allPhoneNumbers;
},

parse: function (text, pattern) {
    var reg = new RegExp(pattern, "gm");
    var r = reg.exec(text);
    this.debug("parsing result = " + r);
    return r;
},

parseLogin: function () {
    this.debug("parsing login");

    this.observerService.removeObserver(this, "http-on-modify-request");

    try {
        this.clearReqTimeout();

        var responseText = this.httpReq.responseText;

        // it's a login request

        // TODO better way to check error
        // TODO what to do if error occurs. retry login?
        if (responseText.indexOf("The System is currently unable to display your current balance") >= 0) {
            this.finishedChecking();
            this.warn("Verizon site is currently unable to display your current balance");
        } else if (responseText.indexOf("The information you have entered does not match the information we have on file") >=0 ||
            responseText.indexOf("For security reasons, we lock your account after several unsuccessful attempts to login to your online account.") >= 0 ) {
            this.finishedChecking();
            this.setStatusBarAttribute("label", "incorrect user name or password");
            this.setStatusBarAttribute("height", 1);
            this.setStatusBarAttribute("src", this.getPhoneLogo());
            this.error ('Incorrect username/password combination. Please go to the option menu and make sure your username and password are valid.');
        } else {
            this.debug('successfully logged in');

            this.loggedInByMe = true;
            if (responseText.indexOf("Continue to My Account") > 0) {
                var accountLink = this.parse(responseText, 'form action="https://(.*)" method');
                if (accountLink && accountLink.length > 0) {
                    accountLink = "https://" + accountLink[1];
                    accountLink = accountLink.replace(/amp;/g, "");
                    this.debug("continue to account with url " + accountLink);
                    var req = new XMLHttpRequest();
                    // currently using synchronous request. if this intermediate page will be there for a long time, it probably should be asynchronous
                    req.open("POST", accountLink, false);
                    req.send("continue=");
                }
            }
            this.checkMinutes();
        }
    } catch (ex) {
        this.finishedChecking();
        this.warn(ex);
    }
},

startMinutesChecking: function()
{
    this.debug('starting minutes checking');
    var delay = 0;
    if (this.getLastUpdated() != null) {
        var elapsedTime = ((new Date()).getTime() - this.getLastUpdated().getTime());
        delay = this.getUpdateFrequency() - elapsedTime;
    }
    var needToCheck = true;
    if (delay > 0) {
        // instead of being fancy and accurate, simply wait a full cycle before starting the next check
//        delay = Math.floor(delay);
//        this.debug("waiting for " + delay + " ms to start next checking");
//        window.setTimeout("gVerizonMinutesUsed.startMinutesChecking(true)", delay);
        
        var data = "";
        var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
            .createInstance(Components.interfaces.nsIFileInputStream);
        var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
            .createInstance(Components.interfaces.nsIScriptableInputStream);
        fstream.init(this.lastResponseFile, -1, 0, 0);
        sstream.init(fstream); 
        
        var str = sstream.read(4096);
        while (str.length > 0) {
            data += str;
            str = sstream.read(4096);
        }
        
        sstream.close();
        fstream.close();
        
        this.debug("reading last response: ");
        //this.debug(data);

        if (data.length > 0) {
            needToCheck = false;
            this.debug("displaying status based on last response");
            this.parseResponseText(data);
        }
    }        

    if (needToCheck) {
        this.checkMinutesWithLogout(false);
    }

    this.scheduleMinutesChecking(window);
    // in case the option menu turns off async
    this.async = true;

},

scheduleMinutesChecking: function(win)
{
    if (win.gVerizonMinutesUsed != null) {
        win.gVerizonMinutesUsed.intervalId = win.setInterval("gVerizonMinutesUsed.checkMinutesWithLogout()", this.getUpdateFrequency());
    }
},
    
stopMinutesChecking: function()
{
    this.debug ("stopping minutes checking");
    var windowManager= this.getWindowManager();
    var enumerator = windowManager.getXULWindowEnumerator( null );
      
    // clear all intervals that could be there
    while ( enumerator.hasMoreElements()  )
    {
        var xulWindow = enumerator.getNext();
        var win = this.getDOMWindow(xulWindow);
                
        if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId != null)
        {
            //remove the interval check
            this.debug("clearning old scheduled task");
            win.clearInterval(win.gVerizonMinutesUsed.intervalId);
            win.gVerizonMinutesUsed.intervalId = null;
        }
    }
    
},

isMinutesCheckingStarted: function()
{
    var windowManager= this.getWindowManager();
    var enumerator = windowManager.getXULWindowEnumerator( null );
      
    while ( enumerator.hasMoreElements()  )
    {
        var xulWindow = enumerator.getNext();
        var win = this.getDOMWindow(xulWindow);
                
        if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId != null)
        {
            return true;
        }
    }
        
    return false;
},


// if connectToYahoo is false, we assume the user is already connected
start: function()
{
    // stop the checking if it's already started. the time interval could have changed
    if (this.isMinutesCheckingStarted())
    {   
        this.stopMinutesChecking();
    }
    
    this.startMinutesChecking();
    
},

getWindowManager: function() {
    var mediatorContractid="@mozilla.org/appshell/window-mediator;1";
    var nsIWindowMediator=Components.interfaces.nsIWindowMediator;
    return Components.classes[mediatorContractid].getService(nsIWindowMediator);
},

getDOMWindow : function(xulWindow) {
    xulWindow = xulWindow.QueryInterface( Components.interfaces.nsIXULWindow);
    var requestor = xulWindow.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
    return requestor.getInterface(Components.interfaces.nsIDOMWindow);
},

// there can be more than 1 status bar
setStatusBarAttribute: function(attr, value)
{
    var windowManager = this.getWindowManager();
    var enumerator = windowManager.getXULWindowEnumerator( null );
    while ( enumerator.hasMoreElements()  )
    {
        var xulWindow = enumerator.getNext();
        xulWindow = xulWindow.QueryInterface( Components.interfaces.nsIXULWindow);
        var myPanel = xulWindow.docShell.contentViewer.DOMDocument.getElementById(this.minutesStatusBarId);
        if (myPanel != null) {
            myPanel.setAttribute(attr, value);
        }
    }
},

addTooltipLabel: function (text, styleClass) {
    var lbl = document.createElement("label");
    lbl.setAttribute("value", text);
    if (styleClass != null) {
        lbl.setAttribute("class", styleClass);
    }
    this.tooltipLabels[this.tooltipLabelCount++] = lbl;
},

cloneTooltipLabel: function(doc, label2) {
    var label1 = doc.createElement("label");
    label1.setAttribute("value", label2.value);
    label1.setAttribute("class", label2.getAttribute("class"));
    return label1;
},

setStatusBarTooltip: function(labels)
{
    var windowManager = this.getWindowManager();
    var enumerator = windowManager.getXULWindowEnumerator( null );
    while ( enumerator.hasMoreElements()  )
    {
        var xulWindow = enumerator.getNext();
        var win = this.getDOMWindow(xulWindow);
        var doc = xulWindow.docShell.contentViewer.DOMDocument;
        if (win.gVerizonMinutesUsed != null) {
            win.gVerizonMinutesUsed.setTooltip(doc, labels);
        }       
    }
},

setTooltip: function(doc, labels) {
    var tooltip, parent;
    if (this.minutesStatusBar == null) {
        this.debug("null minutes status bar. skip setting tooltip");
        return;
    }

    this.debug("setting tooltip");
    if (this.minutesStatusBar.hasAttribute("tooltiptext")) {
        this.minutesStatusBar.removeAttribute("tooltiptext");
    }
    if (this.minutesStatusBar.hasAttribute("tooltip")) {
        tooltip = doc.getElementById(this.minutesStatusBar.getAttribute("tooltip"));
    }
    
    if (tooltip == null) {
    
        parent = doc.getElementById("mainPopupSet");
        tooltip = this.newTooltip(doc, this.minutesStatusBar);
        
        try {
            parent.appendChild(tooltip);
        } catch (e) {
            this.debug("got exception appendChild " + e);
        }
        
        this.minutesStatusBar.setAttribute("tooltip", tooltip.getAttribute("id"));
    }

    while(tooltip.hasChildNodes()) {
        tooltip.removeChild(tooltip.firstChild);
    }

    // if we want to display details for all phone numbers, we can use a hbox containing a list of hbox'es, each having a list of labels
    // uncomment the following code and replace tooltip with rootBox
//     var rootBox = doc.createElement("hbox");
//     for (var k = 0; k < arrayOfLabels.length; k++) {
//         var box = doc.createElement("hbox");
//         box.setAttribute("orient", "vertical");    
//         var labels = arrayOfLabels[k];
    for (var i = 0; i < labels.length; i++) {
        if (labels[i].ownerDocument == doc) {
            tooltip.appendChild(labels[i]);
        } else {
            var label = this.cloneTooltipLabel(doc, labels[i]);
            tooltip.appendChild(label);
        }
    }
//     rootBox.appendChild(box);
//     }
//     tooltip.appendChild(rootBox);

},

newTooltip: function(doc, minutesStatusBar) {
   var tooltip = doc.createElement("tooltip");
   tooltip.setAttribute("id", minutesStatusBar.getAttribute("id") + "-tip");
   tooltip.setAttribute("noautohide", true);
   tooltip.setAttribute("orient", "vertical");
   return tooltip;
},

checkNow: function ()
{
    this.debug("checking usage now");
    var windowManager= this.getWindowManager();
    var enumerator = windowManager.getXULWindowEnumerator( null );
      
    while ( enumerator.hasMoreElements()  )
    {
        var xulWindow = enumerator.getNext();
        var win = this.getDOMWindow(xulWindow);
                
        if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId != null)
        {
            //            win.gVerizonMinutesUsed.setStatusBarAttribute("label", "checking usage...");
            //            win.gVerizonMinutesUsed.setStatusBarAttribute("style", "");
            win.setTimeout("gVerizonMinutesUsed.checkMinutesWithLogout()", 1000);
            return;
        }
    }
},

gotoBillingSite: function ()
{
    if (gBrowser) {
        gBrowser.selectedTab = gBrowser.addTab(this.getBillingURL());
    } else {
        window.open(this.getBillingURL());
    }
},

readHTTPHeaders: function (url) {
   try {
   //would be nice to do this without calling HEAD
     var httpRequest = new XMLHttpRequest();
     httpRequest.open('HEAD', url, false);
     httpRequest.send(null);
     return httpRequest.getAllResponseHeaders();
   }
   catch (e) {
     alert("error trying to create/use XMLHttpRequest");
     return "";
   }
},

findTransporrent: function (headers) {
	var matchArray = headers.match(/X-Transporrent:\s[^\n]*/);
	if(matchArray.length < 1)
		alert("No transporrent header found!");
	else if(matchArray.length > 1)
		alert("Too many transporrent headers found!");
		
    var relevantLine = matchArray[0];
	
    return relevantLine.replace(/X-Transporrent:\ssupplement-/, "http://");
},

getTorrentLink: function ()
{
  var headers = this.readHTTPHeaders(gBrowser.selectedBrowser.currentURI.spec);
	alert("found headers: "+headers);
  return this.findTransporrent(headers);
},

gotoAboutSite: function ()
{
    if (gBrowser) {
        gBrowser.selectedTab = gBrowser.addTab(this.getTorrentLink());
    } else {
		alert("not in gbrowswer");
        window.open(this.getTorrentLink());
    }
},

getLastUpdated: function () {
    if(this.prefBranch.prefHasUserValue("lastUpdated")) {
        return new Date(parseInt(this.prefBranch.getCharPref("lastUpdated")));
    } else {
        return null;
    }
},

setLastUpdated: function (date) {
    this.prefBranch.setCharPref("lastUpdated", date.getTime());
},

setLastUpdatedToNow: function () {
    this.setLastUpdated(new Date());
},

initOption: function () {
    // Load Prefs
    var prefs    = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
    this.prefBranch = prefs.getBranch("extensions.verizon-minutes.");

    if (this.usePwdMgr) {
        this.pwdmgrInternal = Components.classes["@mozilla.org/passwordmanager;1"].getService(Components.interfaces.nsIPasswordManagerInternal);
        this.pwdmgr = Components.classes["@mozilla.org/passwordmanager;1"].getService(Components.interfaces.nsIPasswordManager);
    }
},    
    
optionEditLoad: function(){
    this.debug("loading preference");
    this.initOption();

    var loginData = this.getLoginData();
    if (loginData.username != "") {
        document.getElementById("verizon-minutes-username-field").value = loginData.username;
    }        
    if (loginData.password != "") {
        document.getElementById("verizon-minutes-password-field").value = loginData.password;
    }
    if (loginData.number != "") {
    	document.getElementById("verizon-minutes-number-field").value = loginData.number;
    }
    
    this.oldUsername = loginData.username;

    // show family total
    var el = document.getElementById("verizon-minutes-show-family-total-field");
    var showFamilyTotal = this.getShowFamilyTotal();
    if (showFamilyTotal == 0) {
        el.selectedItem = document.getElementById("verizon-minutes-show-family-total-no");
    } else {
        el.selectedItem = document.getElementById("verizon-minutes-show-family-total-yes");
    }
    
    // east/west
    var el = document.getElementById("verizon-minutes-region-field");
    var region = this.getRegion();
    if (region == 0) {
        el.selectedItem = document.getElementById("verizon-minutes-region-east");
    } else {
        el.selectedItem = document.getElementById("verizon-minutes-region-west");
    }
    
    // summary what
    var el = document.getElementById("verizon-minutes-summary-field");
    var summaryWhat = this.getSummaryWhat();
    if (summaryWhat == 0) {
        el.selectedItem = document.getElementById("verizon-minutes-summary-peak");
    } else {
        el.selectedItem = document.getElementById("verizon-minutes-summary-peak_and_off_peak");
    }



	// new / old logo  (added by Mike Schmitt)
	var el = document.getElementById("verizon-logo-selector-field");
	var logoWhich = this.getLogoWhich();
	if (logoWhich == 0) {
		el.selectedItem = document.getElementById("verizon-logo-selector-big");
	} else if (logoWhich == 1) {
		el.selectedItem = document.getElementById("verizon-logo-selector-small");
	} else {
		el.selectedItem = document.getElementById("verizon-logo-selector-none");
	}


    
    // frequency
    document.getElementById("verizon-minutes-frequency-field").value = this.getUpdateFrequency()/60000;
    
    // threshold
    if (this.getThreshold() <= 0) {
        document.getElementById("verizon-minutes-threshold-field").value = "";
    } else if (this.getThreshold() != this.defaultThreshold) {
        document.getElementById("verizon-minutes-threshold-field").value = this.getThreshold();
    }

    // http timeout
    document.getElementById("verizon-minutes-httptimeout-field").value = this.getHttpTimeout()/1000;
},

// Save Preferences
optionEditSave: function (andClose){
    this.debug("saving preference");    
    try{

        var username = document.getElementById("verizon-minutes-username-field").value;
        var password = document.getElementById("verizon-minutes-password-field").value;
        var number = document.getElementById("verizon-minutes-number-field").value;
        var showFamilyTotal = document.getElementById("verizon-minutes-show-family-total-field").selectedItem.value;
        var region = document.getElementById("verizon-minutes-region-field").selectedItem.value;
        var summaryWhat = document.getElementById("verizon-minutes-summary-field").selectedItem.value;
	var logoWhich = document.getElementById("verizon-logo-selector-field").selectedItem.value;
        var frequency = document.getElementById("verizon-minutes-frequency-field").value;
        var threshold = document.getElementById("verizon-minutes-threshold-field").value;
        var httpTimeout = document.getElementById("verizon-minutes-httptimeout-field").value;

        if (username == "" || username.length == 0) {
            alert("You must enter a non-empty user name");
            document.getElementById("verizon-minutes-username-field").focus();
            return false;
        }
        
        if (frequency == "" || isNaN(frequency)) {
            alert("You must enter a number for update frequency");
            document.getElementById("verizon-minutes-username-field").focus();
            return false;
        }
        
        if (number != "") {
            if (number.length != 10 || isNaN(number)) {
                alert("The phone number must be 10 digit");
                document.getElementById("verizon-minutes-number-field").focus();
                return false;
            }
        }
        
        if (threshold != "" && isNaN(threshold)) {
            alert("You must enter a number for the threshold");
            document.getElementById("verizon-minutes-threshold-field").focus();
            return false;
        }
        
        if (httpTimeout == "" || isNaN(httpTimeout)) {
            alert("You must enter a number for HTTP timeout");
            document.getElementById("verizon-minutes-httptimeout-field").focus();
            return false;
        }
        
        if (!this.usePwdMgr) {
            // username
            this.prefBranch.setCharPref("username", username);
            
            // password
            this.prefBranch.setCharPref("password", password);
        } else {
            try {
                for (var i = 0; i < this.passwordSites.length; i++) {
                    this.pwdmgr.removeUser(this.passwordSites[i], this.oldUsername);
                }
            } catch (err) {
                this.debug("exception removing password " + err);
            }
            for (var i = 0; i < this.passwordSites.length; i++) {
                this.pwdmgrInternal.addUserFull(this.passwordSites[i], username, password, this.usernameFormField, this.passwordFormField);
            }
            this.debug("updated username and password");
        }
        
        //number
        this.prefBranch.setCharPref("number", number);

        // show family total
        var oldValue = this.getShowFamilyTotal();
        var needToLogout = false;
        if (showFamilyTotal == 0 && oldValue == 1) {
            // Verizon caches your family plan choice for the whole session
            needToLogout = true;
        }
        
        this.prefBranch.setIntPref("showFamilyTotal", showFamilyTotal);

        // region
        this.prefBranch.setIntPref("region", region);
        
        // summary what
        this.prefBranch.setIntPref("summaryWhat", summaryWhat);

	// Logo
	this.prefBranch.setIntPref("logoWhich", logoWhich);
        
        // frequency
        var needToChangeInterval = false;
        var frequencyValue = parseInt(frequency,10);
        if (this.getUpdateFrequency() != frequencyValue) {
            this.prefBranch.setIntPref("frequency", frequencyValue);
            needToChangeInterval = true;
        }

        // threshold
        this.prefBranch.setIntPref("threshold", parseInt(threshold,10));

        // http timeout
        this.prefBranch.setIntPref("httpTimeout", parseInt(httpTimeout,10));
                        
        try{
            if(andClose && opener)
                opener.focus();
        } catch(err) {  }

        // after preference is changed, re-check minutes, because the credential could have been corrected
        // however, if the credential is changed from correct to invalid, since we already successfully logged in to the site, it won't notice the difference. (i.e. we can still check the minutes.)
        // restart the timer
        var windowManager= this.getWindowManager();
        var enumerator = windowManager.getXULWindowEnumerator( null );
        while ( enumerator.hasMoreElements() ) {
            var xulWindow = enumerator.getNext();
            var win = this.getDOMWindow(xulWindow);
            if (win.gVerizonMinutesUsed != null && win.gVerizonMinutesUsed.intervalId != null) {
                if (needToLogout) {
                    this.debug("logging out first");
                    var httpReq = new XMLHttpRequest();
                    httpReq.open("GET", this.getLogoutURL(), false);
                    httpReq.send(null);
                }
                if (needToChangeInterval) {
                    this.debug("changing interval to " + this.getUpdateFrequency());
                    win.clearInterval(win.gVerizonMinutesUsed.intervalId);
                    win.gVerizonMinutesUsed.scheduleMinutesChecking(win);
                }

                // cannot start it here in the option window because we want to close it quickly without blocking. so we schedule a new checking very quickly.
                win.setTimeout("gVerizonMinutesUsed.checkMinutesWithLogout()", 1000);
                break;
            }
        }

        return true;
        
    } catch(err) { alert("An unknown error occurred\n"+ err);}

    return false;
},

getLoginData:function() {
    var data = {username: "", password: "", number:""};
    if (this.usePwdMgr) {
        for (var i = 0; i < this.passwordSites.length; i++) {
            var host = {value: this.passwordSites[i]}, user = {value: ""}, password = {value: ""};
            try {
                this.pwdmgrInternal.findPasswordEntry( host.value, '', '', host, user, password ); 
                this.debug("found user/password from the password manager by host " + this.passwordSites[i]);
                break;
            } catch (err) {
                this.debug("no user/password retrieved from the password manager by host " + this.passwordSites[i]);
            }
        }
        data.username = user.value;
        data.password = password.value;
    } else {
        if(this.prefBranch.prefHasUserValue("username")) {        
            data.username = this.prefBranch.getCharPref("username");
        }
        if(this.prefBranch.prefHasUserValue("password")) {
            data.password = this.prefBranch.getCharPref("password");
        }
    }
    if(this.prefBranch.prefHasUserValue("number")) {        
        data.number = this.prefBranch.getCharPref("number");
    }
    return data;
},

getShowFamilyTotal: function () {
    try {
        if(this.prefBranch.prefHasUserValue("showFamilyTotal")) {
            return this.prefBranch.getIntPref("showFamilyTotal");
        } else {
            return 0;
        }
    } catch (err) {
        this.warn(err);
        return 0;
    }
},

getRegion: function () {
    try {
        if(this.prefBranch.prefHasUserValue("region")) {
            return this.prefBranch.getIntPref("region");
        } else {
            return 0;
        }
    } catch (err) {
        this.warn(err);
        return 0;
    }
},



getLogoWhich: function () {
	try {
		if(this.prefBranch.prefHasUserValue("logoWhich")) {
			return this.prefBranch.getIntPref("logoWhich");
		} else {
			return 0;
		}
	} catch (err) {
		this.warn(err);
		return 0;
	}
},



getSummaryWhat: function () {
    try {
        if(this.prefBranch.prefHasUserValue("summaryWhat")) {
            return this.prefBranch.getIntPref("summaryWhat");
        } else {
            return 0;
        }
    } catch (err) {
        this.warn(err);
        return 0;
    }
},

getUpdateFrequency: function () {
    try {
        if(this.prefBranch.prefHasUserValue("frequency")) {
            return this.prefBranch.getIntPref("frequency")*60000;
        } else {
            return this.defaultFrequency;
        }
    } catch (err) {
        this.warn(err);
        return this.defaultFrequency;
    }
},

getThreshold: function () {
    try {
        if(this.prefBranch.prefHasUserValue("threshold")) {
            return this.prefBranch.getIntPref("threshold");
        } else {
            return this.defaultThreshold;
        }
    } catch (err) {
        this.warn(err);
        return this.defaultThreshold;
    }
},

getHttpTimeout: function () {
    try {
        if(this.prefBranch.prefHasUserValue("httpTimeout")) {
            return this.prefBranch.getIntPref("httpTimeout")*1000;
        } else {
            return this.defaultHttpTimeout;
        }
    } catch (err) {
        this.warn(err);
        return this.defaultHttpTimeout;
    }
},

callInProgress: function (xmlhttp) {
    this.debug("xmlhttp ready state = " + xmlhttp.readyState);
    switch ( xmlhttp.readyState ) {
        case 1:
        case 2:
        case 3:
            return true;
            break;

            // Case 4 and 0
        default:
            return false;
            break;
    }
},

setReqTimeout: function  (url) {
    var httpTimeout = this.getHttpTimeout();
    this.timeoutId = setTimeout("gVerizonMinutesUsed.timeoutFired('" + url + "');", httpTimeout);
    this.debug("set timeout " + httpTimeout + "(ms) id " + this.timeoutId + " for " + url);
},

timeoutFired : function(url) {
    this.debug("timeout fired for " + url);
    if ( this.callInProgress(this.httpReq) ) {
        this.warn("http request to " + url + " timed out");
        this.finishedChecking();
        this.httpReq.abort();
    } else if (this.httpReq.readyState == 4) {
        // TODO
        // i have seen billing URL timed out but with state = 4 in this timer
        // but parseMinutes is never called, thus resulting the checkingInProgress = true forever
        this.finishedChecking();
    }
},

clearReqTimeout :function() {
    //        if (this.httpReq.readyState == 4 ) {
    // always clear the timeout
    window.clearTimeout(this.timeoutId);
    this.debug("cleared timeout id " + this.timeoutId);        
},

debug: function (msg) {
    dump(this.getDate() + " DEBUG: " + msg + "\n");
},

warn: function (msg) {
    dump(this.getDate() + " WARNING: " + msg + "\n");
    this.setStatusBarAttribute("tooltiptext", msg);
},

error: function (msg) {
    dump(this.getDate() + " ERROR: " + msg + "\n");
    this.setStatusBarAttribute("tooltiptext", msg);
    this.setStatusBarAttribute("style", "color:rgb(255,0,0)");
    alert(msg);
},

// the file lives under %appdata%/Mozilla/Firefox/Profiles/<your profile folder>
dumpToFile: function(text, fileName) {
    var file  = Components.classes["@mozilla.org/file/directory_service;1"]
        .getService(Components.interfaces.nsIProperties)
        .get("ProfD", Components.interfaces.nsIFile);
    file.append(fileName);
    if (!file.exists()) {
        file.create(file.FILE_TYPE, 0700);
    }
    var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
        .createInstance(Components.interfaces.nsIFileOutputStream);
    foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
    foStream.write(text, text.length);
    foStream.close();
},

getDate: function  () {
    var date = new Date();

    var currMin = date.getMinutes();
    currMin = currMin + "";
    if (currMin.length == 1) {
        currMin = "0" + currMin;
    }
    
    var currHour = date.getHours();
    currHour = currHour + "";
    if (currHour.length == 1) {
        currHour = "0" + currHour;
    }
    
    var currSec = date.getSeconds();
    currSec = currSec + "";
    if (currSec.length == 1) {
        currSec = "0" + currSec;
    }
    
    return date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate() + " " + currHour + ":" + currMin + ":" + currSec + ":" + date.getMilliseconds();
}

}



